package org.orbroker

import adapt._
import exception._

import java.sql.ResultSet

/**
 * Query extractor. This is a unifying type for 
 * [[RowExtractor]] and [[JoinExtractor]]. This
 * should not be implemented directly.
 * @author Nils Kilden-Pedersen
 */
sealed trait QueryExtractor[T] {
  private[orbroker] 
  def mapResultSet(rs: ResultSet, receiver: T ⇒ Boolean, adapter: BrokerAdapter): Unit
}

/**
 * Interface for extracting user defined object 
 * from a single row.
 * <p>Implement this row extractor if a query is a simple 
 * non-JOIN query and the type extracted will not be 
 * extracted as part of a JOIN from another query.
 * @see JoinExtractor
 * @author Nils Kilden-Pedersen
 */
trait RowExtractor[T] extends QueryExtractor[T] {
  
  def extract(row: Row): T

  private[orbroker] 
  override def mapResultSet(rs: ResultSet, receiver: T ⇒ Boolean, adapter: BrokerAdapter): Unit = {
    val row = new ResultSetRow(rs, adapter, Map.empty)
    while (rs.next) {
      val value = extract(row)
      if (!receiver(value)) return
    }

  }
}

trait OutParmExtractor[T] extends QueryExtractor[T] with (OutParms => T) {
  def extract(out: OutParms): T
  final def apply(out: OutParms): T = extract(out)

  private[orbroker] 
  override final def mapResultSet(rs: ResultSet, receiver: T ⇒ Boolean, adapter: BrokerAdapter) = 
    throw new IllegalArgumentException("Cannot extract ResultSet using " + getClass)
}

/**
 * Interface for extracting user defined object 
 * from a group of rows.
 * Implement this join extractor if a query is a
 * JOIN query <em>or</em> if this type needs to
 * be extracted from another JOIN query.
 * <p>NOTICE: Extraction should be done in
 * the following sequence:
 * <ol>
 * <li>Row</li>
 * <li>Join.extractOne</li>
 * <li>Join.extractMany (or Join.extractSeq)</li>
 * </ol>
 * @see RowExtractor
 * @author Nils Kilden-Pedersen
 */
trait JoinExtractor[T] extends QueryExtractor[T] {
  /**
   * The set of columns that uniquely distinguishes
   * this object in a result set, typically the columns 
   * that compose the primary key. The query should be 
   * ordered by those columns.
   */
  val key: Set[String]
  def extract(row: Row, join: Join): T

  private[orbroker] 
  override final def mapResultSet(rs: ResultSet, receiver: T ⇒ Boolean, adapter: BrokerAdapter): Unit = {
    if (rs.next) {
      val join = new JoinGroup(key, rs, Map.empty, adapter)
      do {
        join.newGroup()
        val value = extract(join.row, join)
        if (!receiver(value)) return
        if (!join.rsAdvanced) join.rsReadable = rs.next
      } while (join.rsReadable)
    }

  }

}

private[orbroker] final class DefaultExtractor(id: Symbol) extends RowExtractor[Any] {
  def extract(row: Row): Any = try {
    row.columns.size match {
      case 1 ⇒ row.any("1").getOrElse(null)
      case 2 ⇒ (row.any("1").getOrElse(null), row.any("2").getOrElse(null))
      case 3 ⇒ (row.any("1").getOrElse(null), row.any("2").getOrElse(null), row.any("3").getOrElse(null))
      case 4 ⇒ (row.any("1").getOrElse(null), row.any("2").getOrElse(null), row.any("3").getOrElse(null), row.any("4").getOrElse(null))
      case 5 ⇒ (row.any("1").getOrElse(null), row.any("2").getOrElse(null), row.any("3").getOrElse(null), row.any("4").getOrElse(null), row.any("5").getOrElse(null))
      case x ⇒ throw new ConfigurationException(x + " columns available for '" + id + "', and no RowExtractor registered")
    }
  } catch {
    case e: NoSuchElementException ⇒ throw new ConfigurationException("Statement " + id + " contains NULL values. Must register a RowExtractor")
  }
}

private[orbroker] final class SafeJoinExtractor[T](val delegate: JoinExtractor[T]) extends JoinExtractor[T] {
  require(!delegate.key.isEmpty, "No columns defined for key")
  val key = delegate.key.map(_.toUpperCase)
  def extract(row: Row, join: Join): T = delegate.extract(row, join)
  override def equals(any: Any) = this.delegate eq any.asInstanceOf[SafeJoinExtractor[T]].delegate
  override def hashCode = this.delegate.hashCode
}
